﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Algoritmia.DataStructures.Lists;
using Algoritmia.Problems.Geometry;
using Algoritmia.Problems.Geometry.ConvexHull;

namespace Demos.Convexhull
{
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public ObservableCollection<ElementoVisual> ContenidoVisorSimultaneo { get; set; }
        public ObservableCollection<ElementoVisual> ContenidoVisorQuickhull { get; set; }
        public ObservableCollection<ElementoVisual> ContenidoVisorGraham { get; set; }
        public ObservableCollection<ElementoVisual> ContenidoVisorGreedy { get; set; }

        private bool[] _seleccionAlgoritmo;
        public bool[] SeleccionAlgoritmo
        {
            get { return _seleccionAlgoritmo; }
            set
            {
                _seleccionAlgoritmo = value;
                NotifyPropertyChanged("SeleccionAlgoritmo");
            }
        }

        private int _pestañaSeleccionada;
        public int PestañaSeleccionada
        {
            get { return _pestañaSeleccionada; }
            set
            {
                _pestañaSeleccionada = value;
                NotifyPropertyChanged("PestañaSeleccionada");
            }
        }

        private double _anchoVisor;
        public double AnchoVisor
        {
            get { return _anchoVisor; }
            set
            {
                _anchoVisor = value;
                NotifyPropertyChanged("AnchoVisor");
            }
        }

        private double _altoVisor;
        public double AltoVisor
        {
            get { return _altoVisor; }
            set
            {
                _altoVisor = value;
                NotifyPropertyChanged("AltoVisor");
            }
        }

        private double _x;
        public double X
        {
            get { return _x; }
            set
            {
                _x = value;
                NotifyPropertyChanged("X");
            }
        }

        private double _y;
        public double Y
        {
            get { return _y; }
            set
            {
                _y = value;
                NotifyPropertyChanged("Y");
            }
        }

        private double _duracionAnimacion;
        public double DuracionAnimacion
        {
            get { return _duracionAnimacion; }
            set
            {
                _duracionAnimacion = value;
                NotifyPropertyChanged("DuracionAnimacion");
            }
        }

        private int _numeroPuntosInsertar;
        public int NumeroPuntosInsertar
        {
            get { return _numeroPuntosInsertar; }
            set
            {
                _numeroPuntosInsertar = value;
                NotifyPropertyChanged("NumeroPuntosInsertar");
            }
        }

        private QuickhullFinder _quickhull;
        private GrahamConvexHullFinder _graham;
        private ConvexHullSolver _greedy;
        private readonly Random _r;
        private Dictionary<Board, List<Raya>> _almacen;
        private bool _continuar;
        private Line _lineaAnimacion;
        private const int DiametroPunto = 5;

        public ICommand RedimensionarCommand { get; private set; }
        public ICommand ComenzarCommand { get; private set; }
        public ICommand PararCommand { get; private set; }
        public ICommand LimpiarCommand { get; private set; }
        public ICommand CrearPuntoCommand { get; private set; }
        public ICommand GenerarPuntosCommand { get; private set; }


        public ViewModel()
        {
            SeleccionAlgoritmo = new bool[3];
            SeleccionAlgoritmo[0] = true;
            _quickhull = new QuickhullFinder();
            _graham = new GrahamConvexHullFinder();
            _greedy = new ConvexHullSolver();
            _r = new Random(DateTime.Now.Millisecond);
            _almacen = new Dictionary<Board, List<Raya>>();
            _continuar = true;
            _lineaAnimacion = new Line();
            DuracionAnimacion = 200;
            NumeroPuntosInsertar = 10;

            ContenidoVisorSimultaneo = new ObservableCollection<ElementoVisual>();
            ContenidoVisorQuickhull = new ObservableCollection<ElementoVisual>();
            ContenidoVisorGraham = new ObservableCollection<ElementoVisual>();
            ContenidoVisorGreedy = new ObservableCollection<ElementoVisual>();

            RedimensionarCommand = new RelayCommand(Redimensionar, () => true);
            ComenzarCommand = new RelayCommand(Comenzar, PuedoComenzar);
            PararCommand = new RelayCommand(Parar, () => true);
            LimpiarCommand = new RelayCommand(Limpiar, () => true);
            CrearPuntoCommand = new RelayCommand(CrearPunto, () => true);
            GenerarPuntosCommand = new RelayCommand(GenerarPuntos, () => true);
        }


        private void CrearPunto()
        {
            switch (PestañaSeleccionada)
            {
                case 0:
                    ContenidoVisorSimultaneo.Add(new Punto(X, Y, DiametroPunto, DiametroPunto, Brushes.Black));
                    break;
                case 1:
                    ContenidoVisorQuickhull.Add(new Punto(X, Y, DiametroPunto, DiametroPunto, Brushes.Black));
                    ContenidoVisorGraham.Add(new Punto(X, Y, DiametroPunto, DiametroPunto, Brushes.Black));
                    ContenidoVisorGreedy.Add(new Punto(X, Y, DiametroPunto, DiametroPunto, Brushes.Black));
                    break;
            }
        }

        private void Parar()
        {
            _continuar = false;
        }

        private void Redimensionar()
        {
            switch (PestañaSeleccionada)
            {
                case 0:
                    RedimensionarUnico();
                    break;
                case 1:
                    RedimensionarMultiple();
                    break;
            }
        }

        private void RedimensionarUnico()
        {
            var puntosSobrantes = new List<int>();
            var maxX = AnchoVisor;
            var maxY = AltoVisor;
            for (var i = 0; i < ContenidoVisorSimultaneo.Count; i++)
            {
                if (ContenidoVisorSimultaneo[i].Y > maxY || ContenidoVisorSimultaneo[i].X > maxX)
                {
                    puntosSobrantes.Add(i);
                }
            }
            foreach (var puntoSobrante in puntosSobrantes)
            {
                ContenidoVisorSimultaneo.RemoveAt(puntoSobrante);
            }
        }

        private void RedimensionarMultiple()
        {
            var puntosSobrantes = new List<int>();
            var maxX = AnchoVisor;
            var maxY = AltoVisor;
            for (var i = 0; i < ContenidoVisorQuickhull.Count; i++)
            {
                if (ContenidoVisorQuickhull[i].Y > maxY || ContenidoVisorQuickhull[i].X > maxX)
                {
                    puntosSobrantes.Add(i);
                }
            }
            foreach (var puntoSobrante in puntosSobrantes)
            {
                ContenidoVisorQuickhull.RemoveAt(puntoSobrante);
                ContenidoVisorGraham.RemoveAt(puntoSobrante);
                ContenidoVisorGreedy.RemoveAt(puntoSobrante);
            }
        }

        private void Limpiar()
        {
            ContenidoVisorSimultaneo.Clear();
            ContenidoVisorQuickhull.Clear();
            ContenidoVisorGraham.Clear();
            ContenidoVisorGreedy.Clear();
            _continuar = true;
        }

        private void GenerarPuntos()
        {
            ContenidoVisorQuickhull.Clear();
            ContenidoVisorGraham.Clear();
            ContenidoVisorGreedy.Clear();
            for (var i = 0; i < NumeroPuntosInsertar; i++)
            {
                double maxX = AnchoVisor;
                double maxY = AltoVisor;
                var x = _r.NextDouble() * maxX;
                var y = _r.NextDouble() * maxY;
                ContenidoVisorQuickhull.Add(new Punto(x, y, DiametroPunto, DiametroPunto, Brushes.Black));
                ContenidoVisorGraham.Add(new Punto(x, y, DiametroPunto, DiametroPunto, Brushes.Black));
                ContenidoVisorGreedy.Add(new Punto(x, y, DiametroPunto, DiametroPunto, Brushes.Black));
            }
        }

        private bool PuedoComenzar()
        {
            switch (PestañaSeleccionada)
            {
                case 0:
                    if (ContenidoVisorSimultaneo.Count > 0)
                    {
                        return true;
                    }
                    break;
                case 1:
                    if (ContenidoVisorQuickhull.Count > 0)
                    {
                        return true;
                    }
                    break;
            }
            return false;
        }

        private void Comenzar()
        {
            _almacen[Board.Manual] = new List<Raya>();
            _almacen[Board.Quickhull] = new List<Raya>();
            _almacen[Board.Graham] = new List<Raya>();
            _almacen[Board.Greedy] = new List<Raya>();
            switch (PestañaSeleccionada)
            {
                case 0:
                    ComenzarUnico();
                    break;

                case 1:
                    ComenzarMultiple();
                    break;
            }
        }

        private void ComenzarUnico()
        {
            var lineas = (from elementoVisual in ContenidoVisorSimultaneo
                          where elementoVisual.GetType() == typeof(Raya)
                          select elementoVisual as Raya).ToList();
            foreach (var linea in lineas)
            {
                ContenidoVisorSimultaneo.Remove(linea);
            }

            var puntos = new ArrayList<Tuple<double, double>>();
            foreach (var elementoVisual in ContenidoVisorSimultaneo)
            {
                puntos.Add(new Tuple<double, double>(elementoVisual.X, elementoVisual.Y));
            }
            if (SeleccionAlgoritmo[0])
            {
                SolucionarQuickHull(puntos, Board.Manual);
            }
            else if (SeleccionAlgoritmo[1])
            {
                SolucionarGraham(puntos, Board.Manual);
            }
            else if (SeleccionAlgoritmo[2])
            {
                SolucionarGreedy(puntos, Board.Manual);
            }
            DibujarLineaVisual(Board.Manual);
        }

        private void ComenzarMultiple()
        {
            var lineasQh = (from elementoVisual in ContenidoVisorQuickhull
                            where elementoVisual.GetType() == typeof(Raya)
                            select elementoVisual as Raya).ToList();
            foreach (var linea in lineasQh)
            {
                ContenidoVisorQuickhull.Remove(linea);
            }

            var lineasGh = (from elementoVisual in ContenidoVisorGraham
                            where elementoVisual.GetType() == typeof(Raya)
                            select elementoVisual as Raya).ToList();
            foreach (var linea in lineasGh)
            {
                ContenidoVisorGraham.Remove(linea);
            }

            var lineasGr = (from elementoVisual in ContenidoVisorGreedy
                            where elementoVisual.GetType() == typeof(Raya)
                            select elementoVisual as Raya).ToList();
            foreach (var linea in lineasGr)
            {
                ContenidoVisorGreedy.Remove(linea);
            }

            var puntos2 = new ArrayList<Tuple<double, double>>();
            foreach (var elementoVisual in ContenidoVisorQuickhull)
            {
                puntos2.Add(new Tuple<double, double>(elementoVisual.X, elementoVisual.Y));
            }
            SolucionarQuickHull(puntos2, Board.Quickhull);
            SolucionarGraham(puntos2, Board.Graham);
            SolucionarGreedy(puntos2, Board.Greedy);
            DibujarLineaVisual(Board.Quickhull);
            DibujarLineaVisual(Board.Graham);
            DibujarLineaVisual(Board.Greedy);
        }

        private ArrayList<Point2D> PrepararPuntos(IEnumerable<Tuple<double, double>> s)
        {
            var puntos2D = new ArrayList<Point2D>();
            foreach (var tuple in s)
            {
                puntos2D.Add(new Point2D(tuple.Item1, tuple.Item2));
            }
            return puntos2D;
        }

        private void AlmacenarSolucion(IEnumerable<Point2D> solucion, Board objetivo)
        {
            for (var i = 0; i < solucion.Count(); i++)
            {
                if (i != 0)
                {
                    var p1 = new Tuple<double, double>(solucion.ElementAt(i - 1).X, solucion.ElementAt(i - 1).Y);
                    var p2 = new Tuple<double, double>(solucion.ElementAt(i).X, solucion.ElementAt(i).Y);
                    AddLinea(p1, p2, objetivo);
                }
            }
            var pIni = new Tuple<double, double>(solucion.Last().X, solucion.Last().Y);
            var pFin = new Tuple<double, double>(solucion.First().X, solucion.First().Y);
            AddLinea(pIni, pFin, objetivo);
        }

        private void SolucionarQuickHull(IEnumerable<Tuple<double, double>> s, Board objetivo)
        {
            var puntos2D = PrepararPuntos(s);
            var puntos = _quickhull.Quickhull(puntos2D).ToList();
            AlmacenarSolucion(puntos, objetivo);
        }

        private void SolucionarGraham(IEnumerable<Tuple<double, double>> s, Board objetivo)
        {
            var puntos2D = PrepararPuntos(s);
            var puntos = _graham.Find(puntos2D);
            AlmacenarSolucion(puntos, objetivo);
        }

        private void SolucionarGreedy(IEnumerable<Tuple<double, double>> s, Board objetivo)
        {
            var puntos2D = PrepararPuntos(s);
            var puntos = _greedy.Solve(puntos2D);
            AlmacenarSolucion(puntos, objetivo);
        }

        void AddLinea(Tuple<double, double> p1, Tuple<double, double> p2, Board algoritmo)
        {
            var linea = new Raya(p1.Item1, p1.Item2, p2.Item1, p2.Item2);
            _almacen[algoritmo].Add(linea);
        }

        private void DibujarLineaVisual(Board objetivo)
        {
            if (_almacen[objetivo].Count > 0 && _continuar)
            {
                var sb1 = new Storyboard();
                var linea = _almacen[objetivo][0];
                _almacen[objetivo].RemoveAt(0);

                switch (objetivo)
                {
                    case Board.Manual:
                        ContenidoVisorSimultaneo.Add(linea);
                        sb1.Completed += StoryBoardManualCompleted;
                        break;
                    case Board.Quickhull:
                        ContenidoVisorQuickhull.Add(linea);
                        sb1.Completed += StoryBoardQuickhullCompleted;
                        break;
                    case Board.Graham:
                        ContenidoVisorGraham.Add(linea);
                        sb1.Completed += StoryBoardGrahamCompleted;
                        break;
                    case Board.Greedy:
                        ContenidoVisorGreedy.Add(linea);
                        sb1.Completed += StoryBoardGreedyCompleted;
                        break;
                }

                var duration1 = new Duration(TimeSpan.FromMilliseconds(DuracionAnimacion));
                var myDoubleAnimation1 = new DoubleAnimation {Duration = duration1};
                sb1.Duration = duration1;
                sb1.Children.Add(myDoubleAnimation1);
                Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));
                Storyboard.SetTarget(myDoubleAnimation1, _lineaAnimacion);
                myDoubleAnimation1.From = 0;
                myDoubleAnimation1.To = 1;
                sb1.Begin();
            }
        }

        private void StoryBoardGreedyCompleted(object sender, EventArgs e)
        {
            DibujarLineaVisual(Board.Greedy);
        }

        private void StoryBoardGrahamCompleted(object sender, EventArgs e)
        {
            DibujarLineaVisual(Board.Graham);
        }

        private void StoryBoardQuickhullCompleted(object sender, EventArgs e)
        {
            DibujarLineaVisual(Board.Quickhull);
        }

        void StoryBoardManualCompleted(object sender, EventArgs e)
        {
            DibujarLineaVisual(Board.Manual);
        }
    }
}
